<?php

namespace Kangcg\Application;

use Closure;
use Kangcg\Application\Base\Behavior;
use Kangcg\Application\Base\ObjectAssistance;
use Kangcg\Application\Base\Event;
use Kangcg\Application\Base\Trait\ErrorsTrait;
use Kangcg\Application\Base\Trait\ExtendTrait;
use Kangcg\Application\Cache\FileCache;
use Kangcg\Application\Http\HttpRequest;

/**
 * @property FileCache $cache
 * @property HttpRequest $curl
 */
class Component extends ObjectAssistance
{
    use ErrorsTrait, ExtendTrait;

    // 存储绑定的依赖项
    private static array $bindings = [];
    // 存储单例实例
    private static array $instances = [];

    private $providers = [
        'cache' => FileCache::class,
        'curl' => HttpRequest::class,
    ];

    public function __construct($config = [])
    {
        $this->setConfig($config);
        $this->registerProviders(array_merge($this->providers, $this->getProviders()));
        $this->__initialization();
    }

    /**
     * 获取该类扩展为该类提供服务
     * @return array
     */
    public function behaviors(): array
    {
        return [];
    }

    /**
     * 事件触发
     * @param string $name 事件名称
     * @param Event|null $event
     */
    public function trigger(string $name, Event $event = null)
    {
        $this->ensureBehaviors();
        Event::trigger($this, $name, $event);
    }

    public function registerProviders(array $providers)
    {
        foreach ($providers as $abstract => $concrete) {
            static::bind($abstract, $concrete, true);
        }

        return $this;
    }

    protected function getProviders(): array
    {
        return [

        ];
    }

    /**
     * 添加事件监听
     * @param string $name 事件名称
     * @param array | Closure $handler
     * @param bool $append
     * @return $this
     * @throws ErrorsTrait
     */
    public function on($name, $handler, $append = true)
    {
        $this->ensureBehaviors();
        Event::on($this, $name, $handler, $append);
        return $this;
    }

    /**
     * 解除事件
     * @param string $name 事件名称
     * @return $this
     * @throws ErrorsTrait
     */
    public function off($name)
    {
        $this->ensureBehaviors();
        Event::off($this, $name);
        return $this;
    }

    public static function has($abstract)
    {
        return isset(static::$instances[$abstract]) || isset(static::$bindings[$abstract]);
    }

    /**
     * 绑定一个依赖项到容器中
     * @param string $abstract 抽象名称（通常是类名或接口名）
     * @param mixed $concrete 具体实现，可以是类名、闭包或实例
     * @param bool $shared 是否为单例模式
     */
    public static function bind($abstract, $concrete, $shared = false)
    {
        if (!$concrete instanceof \Closure) {
            $concrete = self::getClosure($abstract, $concrete);
        }

        self::$bindings[$abstract] = compact('concrete', 'shared');
    }

    public function getId($abstract)
    {
        if (!static::has($abstract)) {
            throw new \Exception($abstract . ' is not registered');
        }

        if (isset(static::$instances[$abstract])) {
            return static::$instances[$abstract];
        }

        return static::make($abstract);
    }

    /**
     * 绑定一个单例依赖项到容器中
     * @param string $abstract 抽象名称
     * @param mixed $concrete 具体实现
     */
    public static function singleton($abstract, $concrete = null)
    {
        self::bind($abstract, $concrete, true);
    }

    /**
     * 解析依赖项
     * @param string $abstract
     * @param array $parameters
     * @return mixed|object|null
     * @throws \ReflectionException
     */
    public static function make(string $abstract, array $parameters = [])
    {
        if (isset(self::$instances[$abstract])) {
            return self::$instances[$abstract];
        }

        $concrete = self::getConcrete($abstract);
        if (self::isBuildable($concrete, $abstract)) {
            $object = self::build($concrete, $parameters);
        } else {
            $object = self::make($concrete, $parameters);
        }

        if (self::isShared($abstract)) {
            self::$instances[$abstract] = $object;
        }

        return $object;
    }

    /**
     * 判断是否单例模式
     * @param string $abstract
     * @return bool
     */
    private static function isShared($abstract)
    {
        return isset(self::$bindings[$abstract]['shared']) && self::$bindings[$abstract]['shared'];
    }

    /**
     * 获取实现方法
     * @param $abstract
     * @return mixed
     */
    private static function getConcrete($abstract)
    {
        if (!isset(self::$bindings[$abstract])) {
            return $abstract;
        }

        return self::$bindings[$abstract]['concrete'];
    }

    /**
     * 判断是否可以直接实现
     * @param $concrete
     * @param $abstract
     * @return bool
     */
    private static function isBuildable($concrete, $abstract)
    {
        return $concrete === $abstract || $concrete instanceof \Closure;
    }

    /**
     * 根据依赖注入实现实例
     * @param $concrete
     * @param array $parameters
     * @return mixed|object|null
     * @throws \ReflectionException
     */
    private static function build($concrete, array $parameters = [])
    {
        if ($concrete instanceof \Closure) {
            return $concrete(null, $parameters);
        }

        $reflector = new \ReflectionClass($concrete);
        if (!$reflector->isInstantiable()) {
            throw new \Exception("Target [$concrete] is not instantiable.");
        }

        $constructor = $reflector->getConstructor();
        if (is_null($constructor)) {
            return new $concrete;
        }

        $dependencies = $constructor->getParameters();
        $parameters = self::resolveDependencies($dependencies, $parameters);
        return $reflector->newInstanceArgs($parameters);
    }

    private static function resolveDependencies(array $dependencies, array $parameters = [])
    {
        $results = [];
        foreach ($dependencies as $dependency) {
            $name = $dependency->getName();
            if (array_key_exists($name, $parameters)) {
                $results[] = $parameters[$name];
            } elseif ($dependency->getClass()) {
                $results[] = self::make($dependency->getClass()->name);
            } elseif ($dependency->isDefaultValueAvailable()) {
                $results[] = $dependency->getDefaultValue();
            } else {
                throw new \Exception("Unresolvable dependency resolving [$name] in class {$dependency->getDeclaringClass()->getName()}");
            }
        }

        return $results;
    }

    private static function getClosure($abstract, $concrete)
    {
        return function ($container, $parameters = []) use ($abstract, $concrete) {
            return self::build($concrete, $parameters);
        };
    }

    /**
     * @throws ErrorsTrait
     */
    private function ensureBehaviors()
    {
        if ($this->_behaviors === null) {
            $this->_behaviors = [];
            foreach ($this->behaviors() as $name => $behavior) {
                $this->attachBehaviorInternal($name, $behavior);
            }
        }
    }

    /**
     * @param $name
     * @param $behavior
     * @return Behavior|mixed
     * @throws ErrorsTrait
     */
    private function attachBehaviorInternal($name, $behavior)
    {
        if (empty($behavior)) {
            return null;
        }

        try {
            if (!is_object($behavior)) {
                $behavior = new $behavior;
            }

            if ($behavior instanceof Behavior) {
                $behavior->attach($this);
            }
        } catch (\Exception $exception) {
            throw new \Exception($exception->getMessage(), 1, $exception);

        }

        if (is_int($name)) {
            $this->_behaviors[] = $behavior;
        } else {
            $this->_behaviors[$name] = $behavior;
        }

        return $behavior;
    }

    /**
     * @param $name
     * @param $value
     */
    public function __set($name, $value)
    {
        $attr = 'set' . ucfirst($name);
        if (method_exists($this, $attr)) {
            $this->{$attr}($value);
        } else {
            $this->_config[$name] = $value;
        }
    }

    /**
     * @param $name
     * @return |null
     */
    public function __get($name)
    {
        if (static::has($name)) {
            return $this->getId($name);
        }

        $attr = 'get' . ucfirst($name);
        if (method_exists($this, $attr)) {
            return $this->{$attr}();
        }

        return $this->offsetGet($name);
    }


    protected function __initialization(): void
    {

    }

    private $_behaviors = null;
    private $_extends = null;
}
